/*******************************************************************************
 * AAEngine
 * Copyright (c) 2010 Mike Jarosch
 *
 * Original source PushButton Engine:
 * Copyright (C) 2009 PushButton Labs, LLC
 * For more information see http://www.pushbuttonengine.com
 *
 * This file is licensed under the terms of the MIT license, which is included
 * in the "MIT License.txt" file at the root directory of this SDK.
 ******************************************************************************/

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Box2D.XNA;
using AAEngine.Engine;
using AAEngine.Engine.Core;
using AAEngine.Engine.Debug;
using AAEngine.Engine.Entities;
using AAEngine.Rendering2D;

namespace AAEngine.Box2D
{
    public class Box2DManagerComponent : EntityComponent, ITickedObject, ISpatialManager2D
    {
        private World _world = null;
        private float _scale = 1;
        private bool _allowSleep = true;
        private Vector2 _gravity = new Vector2(0f, 9.81f);
        private Box _worldBounds = new Box(new Vector2(-5000f, -5000f), new Vector2(10000f, 10000f));
        private BasicSpatialManager2D _otherItems = new BasicSpatialManager2D();

        public float Scale 
        { 
            get { return _scale; }
            set { _scale = value; }
        }

        public float InverseScale { get { return 1f / Scale; } }

        public World World { get { return _world; } }

        public bool AllowSleep
        {
            get { return _allowSleep; }
            set
            {
                if (_world != null)
                {
                    Logger.Warn(this, "AllowSleep", "This property cannot be changed once the world has been created.");
                    return;
                }

                _allowSleep = value;
            }
        }

        public Vector2 Gravity
        {
            get { return _gravity; }
            set
            {
                _gravity = value;

                if (World != null)
                {
                    World.Gravity = value;
                }
            }
        }

        public Box WorldBounds
        {
            get { return _worldBounds; }
            set
            {
                if (_world != null)
                {
                    Logger.Warn(this, "WorldBounds", "This property cannot be changed once the world has been created.");
                    return;
                }

                _worldBounds = value;
            }
        }

        public override void OnAdd()
        {
            AAGame.ProcessManager.AddTickedObject(this);
            CreateWorld();
        }

        public override void OnRemove()
        {
            Body body = _world.GetBodyList();

            //the world is locked. this was called from a collision or other event.
            //defer until we know it should not be locked anymore.
            if (_world.IsLocked)
            {
                AAGame.ProcessManager.CallLater(() => { OnRemove(); });
            }
            else
            {
                while (body != null)
                {
                    Body next = body.GetNext();
                    _world.DestroyBody(body);
                    body = next;
                }

                _world = null;

                AAGame.ProcessManager.RemoveTickedObject(this);
            }
        }

#if WINDOWS_PHONE || XBOX
        public void Add(BodyDef bodyDef)
        {
            Add(bodyDef, null);
        }

        public void Add(BodyDef bodyDef, Action<Body> completedCallback)
#else
        public void Add(BodyDef bodyDef, Action<Body> completedCallback = null)
#endif
        {
            if (_world == null)
            {
                throw new AAException("World not initialized.");
            }

            if (_world.IsLocked)
            {
                AAGame.ProcessManager.CallLater(() => { Add(bodyDef, completedCallback); });
            }
            else
            {
                Body body = _world.CreateBody(bodyDef);
                if (completedCallback != null)
                {
                    completedCallback(body);
                }
            }
        }

        public void Remove(Body body)
        {
            if (_world != null)
            {
                if (_world.IsLocked)
                {
                    AAGame.ProcessManager.CallLater(() => { Remove(body); });
                }
                else
                {
                    _world.DestroyBody(body);
                }
            }
        }

        public void SetDebugDrawer(DebugDraw drawer)
        {
            _world.DebugDraw = drawer;
        }

        public void OnTick(float deltaTime)
        {
            _world.Step(deltaTime, 3, 3);
            _world.ClearForces();
        }

        public void AddSpatialObject(ISpatialObject2D spatialObject)
        {
            _otherItems.AddSpatialObject(spatialObject);
        }

        public void RemoveSpatialObject(ISpatialObject2D spatialObject)
        {
            _otherItems.RemoveSpatialObject(spatialObject);
        }

        public bool QueryRectangle(Box box, ObjectType mask, ref IList<ISpatialObject2D> results)
        {
            AABB aabb = new AABB();
            aabb.lowerBound = box.Min;
            aabb.upperBound = box.Max;

            return false;
        }

        public bool QueryCircle(Vector2 center, int radius, ObjectType mask, ref IList<ISpatialObject2D> results)
        {
            return false;
        }

        public bool QueryPoint(Vector2 point, ObjectType mask, ref IList<ISpatialObject2D> results)
        {
            return false;
        }

        public bool CastRay(Vector2 start, Vector2 end, ObjectType mask, ref RayHitInfo result)
        {
            return _otherItems.CastRay(start, end, mask, ref result);
        }

        private void CreateWorld()
        {
            //AABB bounds = new AABB();
            //bounds.lowerBound = _worldBounds.Min;
            //bounds.upperBound = _worldBounds.Max;
            _world = new World(_gravity, _allowSleep);
            World.ContactFilter = new ContactFilter();
            World.ContactListener = new ContactListener();
        }
    }
}
